﻿using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace GameLibrary
{
    /// <summary>
    /// The camera used in the game. It follows the players around the level and
    /// uses the average of all player positions to calculate its own position.
    /// It also has a bounding box which is used in the drawing and in the physics etc.
    /// </summary>
    /// <remarks></remarks>
    public class Camera : ICamera
    {
        /// <summary>
        /// Gets or sets the camera position.
        /// </summary>
        /// <value>The position.</value>
        /// <remarks></remarks>
        public Vector2 Position { get; set; }

        /// <summary>
        /// Gets or sets the camera origin.
        /// </summary>
        /// <value>The origin.</value>
        /// <remarks></remarks>
        public Vector2 Origin { get; set; }

        /// <summary>
        /// Gets or sets the camera zoom.
        /// </summary>
        /// <value>The zoom.</value>
        /// <remarks></remarks>
        public float Zoom { get; set; }

        /// <summary>
        /// Gets or sets the camera rotation.
        /// </summary>
        /// <value>The rotation.</value>
        /// <remarks></remarks>
        public float Rotation { get; set; }

        /// <summary>
        /// Gets the camera limits.
        /// </summary>
        /// <remarks></remarks>
        public Vector2 Limit { get; private set; }

        /// <summary>
        /// Gets the bounding box.
        /// </summary>
        /// <remarks></remarks>
        public BoundingBox BoundingBox { get; private set; }

        private Vector2 _origin;
        private Viewport _viewPort;
        private Matrix _view;

        /// <summary>
        /// Initializes a new instance of the Camera class.
        /// </summary>
        /// <param name="viewPort">The view port.</param>
        /// <param name="graphics">The graphics.</param>
        /// <remarks></remarks>
        public Camera(Viewport viewPort, GraphicsDeviceManager graphics)
        {
            _origin = new Vector2(viewPort.Width / 2.0f, viewPort.Height / 2.0f);
            Limit = new Vector2(0, 4000);
            Zoom = 1.0f;
            _viewPort = viewPort;
            CalculateBoundingBox();
        }

        /// <summary>
        /// Updating the view of the camera by getting all the players positions and
        /// computes the average in which it will keep its center
        /// </summary>
        /// <param name="avgPlayerPositions">The avg player positions.</param>
        /// <param name="parallax">The parallax.</param>
        /// <remarks></remarks>
        public void Update(Vector2 avgPlayerPositions, Vector2 parallax)
        {
            if (avgPlayerPositions.X - _viewPort.Width * 0.5f > Limit.X && avgPlayerPositions.X < Limit.Y - _viewPort.Width * 0.5f)
            {
                Position = new Vector2((avgPlayerPositions.X - (_viewPort.Width * 0.5f)), 0);
            }
            CalculateBoundingBox();
            UpdateLookAt(parallax);
        }

        /// <summary>
        /// Calculate the boundingbox which the game uses to
        /// check if objects out of the screen isn't drawn to aid efficiency.
        /// </summary>
        /// <remarks></remarks>
        private void CalculateBoundingBox()
        {
            BoundingBox = new BoundingBox(new Vector3(Position.X, Position.Y, 0), new Vector3(Position.X + _viewPort.Width, Position.Y + _viewPort.Height, 0));
        }

        /// <summary>
        /// Gets the view matrix.
        /// </summary>
        /// <returns> The view matrix.</returns>
        /// <remarks></remarks>
        public Matrix GetViewMatrix()
        {
            return _view;
        }

        /// <summary>
        /// Updates the view matrix with its new position and target.
        /// </summary>
        /// <param name="parallax">The parallax.</param>
        /// <remarks></remarks>
        private void UpdateLookAt(Vector2 parallax)
        {
            _view = Matrix.CreateTranslation(new Vector3(-Position * parallax, 0.0f)) *
                    Matrix.CreateTranslation(new Vector3(-Origin, 0.0f)) *
                    Matrix.CreateRotationZ(Rotation) *
                    Matrix.CreateScale(Zoom, Zoom, 1) *
                    Matrix.CreateTranslation(new Vector3(Origin, 0.0f));
        }

        /// <summary>
        /// Reset the camera view to the starting position of the level. Used when restarting a level
        /// </summary>
        /// <remarks></remarks>
        public void ResetPosition()
        {
            Position = Vector2.Zero;
        }
    }
}